/****************************************************************************

  Copyright (c) PokerSpot International, 1999

  Title    : class StateBet
  Created  : 11.11.1999

  OVERVIEW : Implements one betting round for Texas Hold'em.

 ****************************************************************************/

#include "stdafx.h"
#include "tem/statebet.h"
#include "tem/playerindex.h"
#include "base/player.h"
#include "base/gamelogic.h"
#include "base/draw.h"
#include "base/actionimpl.h"
#include "network/pduaction.h"
#include "network/pduactionrequest.h"
#include "network/pduactionrequestspreadlimit.h"
#include "network/pduallin.h"
#include "network/pduactionecho.h"
#include "ui/actionbar.h"
#include "ui/tableview.h"
#include "ui/global.h"
#include "ui/chatview.h"
#include "ui/mainwnd.h"
#include "ui/soundmanager.h"
#include "ui/cmdid.h"
#include "ui/registry.h"
#include "common/stringoutput.h"

LPCTSTR g_szOutOfTimers = _T("Out of system resources - please quit some applications and restart.");


namespace
{ 
  LPCTSTR g_szToAct       = _T("To Act");
  LPCTSTR g_szFold        = _T("Fold");
  LPCTSTR g_szCheck       = _T("Check");
  LPCTSTR g_szBetFmt      = _T("Bet ");
  LPCTSTR g_szCall        = _T("Call");
  LPCTSTR g_szCallFmt     = _T("Call ");
  LPCTSTR g_szRaiseFmt    = _T("Raise ");
  LPCTSTR g_szWaitingFmt  = _T("Waiting for %s to act");

  StateBet* g_pState = 0;

  // Will use timer instead of idle loop. This means
  // that it's a tad slower but consumes a whole lot
  // less resources than the idle loop.
  #define USE_TIMER_WAIT_ 

  VOID CALLBACK TimerProc(HWND, UINT msg, UINT eventId, DWORD dwTime)
  {
    if (g_pState)
      g_pState->onTimer(msg, eventId, dwTime);
  }
}

using namespace Network;
using Base::GameLogic;

//
// STATE: StateBet
//        Implements one betting round.
//
// Entry: Previous card deal state has finished.
//
// PDUs:  Action Request PDU, Action PDU
//
// Exit:  StateDealCommunity on receiving a Community Card PDU,
//        StateShowdown on receiving a Showdown PDU.
//
StateBet::StateBet()
  :
  ticks_         (0),
  waitActionTime_(0),
  currentPlayer_ (0),
  bet_           (0),
  raise_         (0),
  prompting_     (false),
  pView_         (CTableView::Instance()),
  timer_         (0),
  doChipsAnimate_(-1),
  chipsAmount_   (0),
  doFoldAnimate_ (-1),
  numFoldCards_  (0),
  raiseLo_       (0),
  raiseHi_       (0),
  spreadLimit_   (false)
{
  ASSERT_VALID(pView_);
  CActionBar* pActionBar = CActionBar::Instance();
  ASSERT_VALID(pActionBar);
  pActionBar->enableButtons(0);
}


StateBet::~StateBet()
{
#ifdef USE_TIMER_WAIT_
  g_pState = 0;
  if (timer_ != 0)
    ::KillTimer(NULL, timer_);
  timer_ = 0;
#endif

  CActionBar* pActionBar = CActionBar::Instance();
  ASSERT_VALID(pActionBar);
  if (pActionBar && ::IsWindow(pActionBar->GetSafeHwnd()))
    pActionBar->enableButtons(0);  

  turnPtr_.endTurn();
}


// There is already plenty of going on,
// on a slower machine the animation does not
// get enough idle time and becomes choppy

BOOL StateBet::tick(DWORD dt)
{
  if (doChipsAnimate_ != -1)
  {
    doChipsAnimate_ = -1;
    dt = 1;
  }

  if (doFoldAnimate_ != -1)
  {
    cardsAnim_.startAnimate(doFoldAnimate_,
                            PI_House,
                            numFoldCards_,
                            Global::GetAnimationTime());
    doFoldAnimate_ = -1;
    // start animation with 1 dt
    dt = 1;
  }

  BOOL rc1 = chipsAnim_.stepAnimate(dt);
  BOOL rc2 = cardsAnim_.stepAnimate(dt);

  // if either animation was on, we want
  // more ticks
  return rc1 || rc2;
}


BOOL StateBet::draw(CDC* pDC)
{
  if (prompting_)
    turnPtr_.draw(pDC);

  return TRUE;
}


BOOL StateBet::onCommand(WPARAM wParam, LPARAM lParam)
{
  int cmdId = LOWORD(wParam);
  BOOL rc = FALSE;

  ASSERT_VALID(pView_);

  if (cmdId == ID_ISBETACTION)
  { // Used by UI to decide we're currently
    // prompting for action
    return TRUE;
  }

  // The community player serves as the pot
  Player* pPot = pView_->getPlayer(PI_Community);
  ASSERT(pPot);
  if (!pPot) return FALSE;
  Player* pPlayer = pView_->getPlayer(currentPlayer_);

  switch (cmdId)
  { //
    // Button commands are generated by local user interaction
    //
  case ID_BTN_1:
    rc = onCall();
    CActionBar::UncheckActionButtons();
    break;

  case ID_BTN_2:
    rc = onRaise();
    CActionBar::UncheckActionButtons();
    break;

  case ID_BTN_3:
    rc = onFold();
    CActionBar::UncheckActionButtons();
    break;

  case ID_PDU_ACTIONREQUEST:
  { // 
    // Start prompting the user for an action
    //    
    spreadLimit_ = false;
    rc = onActionRequest(pPot, lParam);
    break;
  }

  case ID_PDU_ACTIONREQUESTSPREADLIMIT:
  {
    // 
    // Start prompting the user for an action
    //    
    spreadLimit_ = true;
    rc = onActionRequestSpreadLimit(pPot, lParam);
    break;
  }

  case ID_PDU_ACTION:
  { //
    // Visualize the action    
    //
    rc = onAction(pPot, lParam);
    break;
  }

  case ID_PDU_ACTIONECHO:
  {
    //
    // The PDU implements the action -
    // end prompting
    //
    endPrompt();

    PDUActionEcho* pPDU = reinterpret_cast<PDUActionEcho*>(lParam);
    if (pPDU)
    {
      // if money moving action, start chips animation
      if (pPDU->action_ == PDUAction::Call &&
          pPDU->amount_ != 0 || 
          pPDU->action_ == PDUAction::Raise)
      {
        SndMan::Sound sound = SndMan::SM_Call;
        doChipsAnimate_ = currentPlayer_;

        if (pPDU->action_ == PDUAction::Call)
        {
          chipsAmount_ = pPDU->amount_;
          sound = SndMan::SM_Call;
        }
        else
        {
          chipsAmount_ = bet_ + pPDU->amount_;//raise_;
          sound = SndMan::SM_Raise;
        }

        // 7-stud has special bring-in action
        if (Global::GetGameType() == GT_SevenStud &&
            pPDU->action_ == PDUAction::Raise &&
            GameLogic::GetIsBringIn())
        {
          chipsAmount_ = pPDU->amount_;
          sound = SndMan::SM_Call;
          doChipsAnimate_ = pPDU->slot_;
        }

        chipsAnim_.startAnimate(doChipsAnimate_,
                                PI_Community,
                                chipsAmount_,
                                sound);
      }

      // If player folds, start fold animation
      if (pPDU->action_ == PDUAction::Fold)
      {
        doFoldAnimate_ = currentPlayer_;
        numFoldCards_ = pPlayer ? pPlayer->numCards() : 0;
      }
    }
    rc = TRUE;
    break;
  }

  case ID_PDU_ALLIN:
  {
    //
    // The PDU implements the action -
    // end prompting
    //
    endPrompt();

    PDUAllIn* pPDU = reinterpret_cast<PDUAllIn*>(lParam);
    if (pPDU)
    {
      SndMan::Sound sound = SndMan::SM_Call;
      doChipsAnimate_ = currentPlayer_;

      CChips playerChips = pPlayer ? pPlayer->getChips() : 0;
      chipsAmount_ = minimum(pPDU->to_pay_, playerChips);
      sound = SndMan::SM_Call;

      if (chipsAmount_ > 0)
      {
        chipsAnim_.startAnimate(doChipsAnimate_,
                                PI_Community,
                                chipsAmount_,
                                sound);
      }
    }
    rc = TRUE;
    break;
  }


  case ID_PDU_GETBET:
  { // Action echo PDU uses this msg to ask
    // for current bet.
    if (lParam != 0)
      *(reinterpret_cast<CChips*>(lParam)) = bet_;
    return TRUE;
  }

  case ID_STOPANIMATION:
  {
    cardsAnim_.stopAnimate();
    chipsAnim_.stopAnimate();
    rc = TRUE;
    break;
  }


  }
    
  if (!rc)
    rc = GameState::onCommand(wParam, lParam);

  return rc;
}


BOOL StateBet::onKey(UINT nChar, UINT nRepCnt, UINT nFlags)
{
  return FALSE;
}


//
// Call - send Action PDU specifying call.
// 
BOOL StateBet::onCall()
{
  Global::Instance().resetActionTimeouts();
  GameLogic::SendCallActionPDU();
  endPrompt();
  return TRUE;
}


//
// Raise - send Action PDU specifying raise.
//
BOOL StateBet::onRaise()
{
  Global::Instance().resetActionTimeouts();
  if (spreadLimit_)
  {
    raise_ = CActionBar::Instance()->getRaise();
    GameLogic::SendSpreadRaiseActionPDU();
    spreadLimit_ = false;
  }
  else
  {
    GameLogic::SendRaiseActionPDU();
  }
  endPrompt();
  return TRUE;
}


//
// Fold - send Action PDU specifying fold.
//
BOOL StateBet::onFold()
{
  if (ticks_ < waitActionTime_ &&
      Global::Instance().getActionTimeouts() < 3)
  {
    Global::Instance().resetActionTimeouts();
  }

  GameLogic::SendFoldActionPDU();
  endPrompt();
  return TRUE;
}


BOOL StateBet::onActionRequest(Player* pPot, LPARAM lParam)
{
  bool playSound = false;
  PDUActionRequest* pPDU = reinterpret_cast<PDUActionRequest*>(lParam);
  ASSERT(IsPDU(pPDU, PDU_Play_ActionRequest));

  Player* pP = pView_->getPlayer(currentPlayer_);
  if (pP)
  { // If previous action request was not finished,
    // zero out the text (this should never happen as
    // the server sends the default action if the user
    // does not respond)
    CString s = pP->getActionText();
    if (s == CString(g_szToAct))
      pP->setActionText("");
    CMainWnd::SetStatusText("");
  }
  
  endPrompt();
  startPrompt(pPDU->msecs_,
              pPDU->player_,
              pPDU->value_,
              pPDU->raise_);

  Player* pPlayer = pView_->getPlayer(currentPlayer_);
  if (pPlayer)
    pPlayer->setActionText(g_szToAct);

  // If the action request is for the local player
  // then update the UI so it prompts the user for
  // an action.

  if (pView_->isLocalPlayer(currentPlayer_) &&
      !Global::IsObserver())
  { // If user has set the call, raise or fold flag on
    // the action bar, do it immediately and don't start
    // waiting for the user's interaction
    
    if (GameLogic::CallInTurn())
    {
      onCall();
      CActionBar::UncheckCallButton();
    }
    else if (pPDU->raise_ != 0 && GameLogic::RaiseInTurn())
    { // Auto-raise only if raise allowed!
      onRaise();
      CActionBar::UncheckRaiseButton();
    }
    else if (GameLogic::FoldInTurn())
    {
      // If bet is 0, then CHECK, otherwise FOLD
      if (pPDU->value_ == 0)
      {
        onCall();
      }
      else
      {
        onFold();
      }
      CActionBar::UncheckFoldButton();
    }
    else
    { 
      playSound = true;
      //
      // Enable action buttons and start waiting for user decision
      //
      CActionBar* pActionBar = CActionBar::Instance();
      ASSERT_VALID(pActionBar);
      pActionBar->stopAnimate(); 
      if (bet_ == 0)
        pActionBar->setButtonText(0, g_szCheck);
      else
      {
        CStrOut s;
        // XXX OLD s.Format(g_szCallFmt, pPDU->value_);
        s << g_szCallFmt << pPDU->value_;
        pActionBar->setButtonText(0, s.str());
      }

      if (pPDU->raise_ != 0)
      { // Enable Call, Raise, Fold
        CStrOut s;
        if (pPDU->value_ == 0)
          // XXX OLD s.Format(g_szBetFmt, pPDU->raise_); // 0 value ==> bet X
          s << g_szBetFmt << pPDU->raise_;
        else
          // XXX OLD s.Format(g_szRaiseFmt, pPDU->raise_); // !0 value ==> raise X
          s << g_szRaiseFmt << pPDU->raise_;
        pActionBar->setButtonText(1, s.str());
        pActionBar->setButtonText(2, g_szFold);
        pActionBar->enableButtons(1 | 2 | 4);
      }
      else
      { // Enable Call, Fold
        pActionBar->setButtonText(2, g_szFold);
        pActionBar->enableButtons(1 | 4);
      }             
    }      
  }
  else
  {
    CActionBar* pActionBar = CActionBar::Instance();
    ASSERT_VALID(pActionBar);
    pActionBar->enableButtons(0);
  }

  // do first timer right away
  onTimer(WM_TIMER, 0, 0);

  if (playSound)
    SndMan::Instance()->playSound(SndMan::SM_YourTurn);

  return TRUE;
}


BOOL StateBet::onActionRequestSpreadLimit(Player* pPot, LPARAM lParam)
{
  bool playSound = false;
  PDUActionRequestSpreadLimit* 
      pPDU = reinterpret_cast<PDUActionRequestSpreadLimit*>(lParam);
  ASSERT(IsPDU(pPDU, PDU_Play_ActionRequestSpreadLimit));
 
  endPrompt();
  startPrompt(pPDU->msecs_,
              pPDU->player_,
              pPDU->value_,
              pPDU->raiseLo_,
              pPDU->raiseHi_);

  Player* pPlayer = pView_->getPlayer(currentPlayer_);
  if (pPlayer)
    pPlayer->setActionText(g_szToAct);

  // If the action request is for the local player
  // then update the UI so it prompts the user for
  // an action.

  if (pView_->isLocalPlayer(currentPlayer_) &&
      !Global::IsObserver())
  { // If user has set the call, raise or fold flag on
    // the action bar, do it immediately and don't start
    // waiting for the user's interaction

    if (GameLogic::CallInTurn())
    {
      onCall();
      CActionBar::UncheckCallButton();
    }
/*
    else if (pPDU->raiseHi_ != 0 && GameLogic::RaiseInTurn())
    { // Auto-raise only if raise allowed!
      onRaise();
      CActionBar::UncheckRaiseButton();
    }
*/
    else if (GameLogic::FoldInTurn())
    {
      // If bet is 0, then CHECK, otherwise FOLD
      if (pPDU->value_ == 0)
      {
        onCall();
      }
      else
      {
        onFold();
      }
      CActionBar::UncheckFoldButton();
    }
    else
    { 
      playSound = true;
      //
      // Enable action buttons and start waiting for user decision
      //
      CActionBar* pActionBar = CActionBar::Instance();
      ASSERT_VALID(pActionBar);
      pActionBar->stopAnimate(); 
      if (bet_ == 0)
        pActionBar->setButtonText(0, g_szCheck);
      else
      {
        CStrOut s;
        s << g_szCallFmt << pPDU->value_;
        pActionBar->setButtonText(0, s.str());
      }

      if (pPDU->raiseLo_ != 0)
      { // Enable Call, Raise, Fold
        CStrOut s;
        if (pPDU->value_ == 0)
          s << g_szBetFmt << pPDU->raiseLo_; // 0 value ==> bet X
        else
          s << g_szRaiseFmt << pPDU->raiseLo_; // !0 value ==> raise X
        pActionBar->setButtonText(1, s.str());
        pActionBar->setButtonText(2, g_szFold);
        pActionBar->enableButtons(1 | 2 | 4);
        pActionBar->setSpread(pPDU->raiseLo_, pPDU->raiseHi_);
      }
      else
      { // Enable Call, Fold
        pActionBar->setButtonText(2, g_szFold);
        pActionBar->enableButtons(1 | 4);
      }             
    }      
  }
  else
  {
    CActionBar* pActionBar = CActionBar::Instance();
    ASSERT_VALID(pActionBar);
    pActionBar->enableButtons(0);
  }

  // do first timer right away
  onTimer(WM_TIMER, 0, 0);

  if (playSound)
    SndMan::Instance()->playSound(SndMan::SM_YourTurn);

  return TRUE;
}


BOOL StateBet::onAction(Player* pPot, LPARAM lParam)
{
  CString s;
  endPrompt();

  PDUAction* pPDU = reinterpret_cast<PDUAction*>(lParam);
  ASSERT(IsPDU(pPDU, PDU_Play_Action));
  if (!pPDU) return FALSE;

  ASSERT_VALID(pView_);
  CChatView* pChatView = CChatView::Instance();
  if (pChatView && !::IsWindow(pChatView->GetSafeHwnd()))
  { // zero out the ptr if the window does not exist any more
    pChatView = NULL;
  }

  Player* pPlayer = pView_->getPlayer(currentPlayer_);

  if (pPlayer)
  {
    if (pPDU->action_ == PDUAction::Call)
    { 
      Base::ImplementCall(pPlayer, currentPlayer_, bet_);
    }
    else if (pPDU->action_ == PDUAction::Raise)
    { 
      Base::ImplementRaise(pPlayer, currentPlayer_, bet_, raise_);
    }
    else if (pPDU->action_ == PDUAction::Fold)
    {
      Base::ImplementFold(pPlayer, currentPlayer_);
    }
  }
  return TRUE;
}


void StateBet::startPrompt(int time, int player, const CChips& value, const CChips& raise)
{
  Player* pPlayer = pView_->getPlayer(player);
  if (!pPlayer) return; // left in the middle of deal!

  turnPtr_.setTurn(player, time);
  turnPtr_.tick(0);

#ifdef USE_TIMER_WAIT_
  g_pState = this;
  if ((timer_ = ::SetTimer(NULL, 0, 500, TimerProc)) == 0)
    pView_->MessageBox(g_szOutOfTimers, NULL, MB_OK|MB_ICONEXCLAMATION);
#endif

  waitActionTime_ = time;
  currentPlayer_  = player;
  bet_            = value;
  raise_          = raise;
  prompting_      = true;

  ASSERT_VALID(pView_);
  bool isForMe = pView_->isLocalPlayer(currentPlayer_);

  CStrOut s;
  if (isForMe)
  { // The request is for me - enable UI
    if (value == 0)
    {
      s << "Check, Bet " << raise << " or Fold";
      CMainWnd::SetStatusText(s.str());
    }
    else
    {
      if (raise > 0)
        s << "Call " << value << ", Raise " << raise << " or Fold";
      else
        s << "Call " << value << " or Fold";
      CMainWnd::SetStatusText(s.str());
    }
  }
  else
  {
    s << "Waiting for " << pPlayer->getName() << " to act";
    CMainWnd::SetStatusText(s.str());
  }
}

void StateBet::startPrompt(int time, int player,
                           const CChips& value,
                           const CChips& raiseLo,
                           const CChips& raiseHi)
{
  Player* pPlayer = pView_->getPlayer(player);
  if (!pPlayer) return; // left in the middle of deal!

  turnPtr_.setTurn(player, time);
  turnPtr_.tick(0);

#ifdef USE_TIMER_WAIT_
  g_pState = this;
  if ((timer_ = ::SetTimer(NULL, 0, 500, TimerProc)) == 0)
    pView_->MessageBox(g_szOutOfTimers, NULL, MB_OK|MB_ICONEXCLAMATION);
#endif

  waitActionTime_ = time;
  currentPlayer_  = player;
  bet_            = value;
  raise_          = raiseLo;
  raiseLo_        = raiseLo;
  raiseHi_        = raiseHi;
  prompting_      = true;

  ASSERT_VALID(pView_);
  bool isForMe = pView_->isLocalPlayer(currentPlayer_);

  CString s;
  if (isForMe)
  { // The request is for me - enable UI
    //s.Format(g_szActStatusFmt, value);
    if (value == 0)
    {
      s.Format("Check, Bet %d-%d or Fold", raiseLo, raiseHi);
      CMainWnd::SetStatusText(s);
    }
    else
    {
      s.Format("Call %d, Raise %d-%d or Fold", value, raiseLo, raiseHi);
      CMainWnd::SetStatusText(s);
    }
  }
  else
  {
    CString n = pPlayer->getName();
    s.Format(g_szWaitingFmt, n);
    CMainWnd::SetStatusText(s);
  }
}

void StateBet::endPrompt()
{
  CMainWnd::SetStatusText("");
#ifdef USE_TIMER_WAIT_
  g_pState = 0;
  if (timer_ != 0)
    ::KillTimer(NULL, timer_);
  timer_ = 0;
#endif

  CActionBar* pActionBar = CActionBar::Instance();
  ASSERT_VALID(pActionBar);
  if (pActionBar)
  {
    pActionBar->stopAnimate();
    pActionBar->enableButtons(0);
  }

  ticks_ = 0;
  turnPtr_.endTurn();

  pView_->UpdateWindow();
}


BOOL StateBet::onTimer(UINT msg, UINT eventId, DWORD dwTime)
{
  ticks_ += 500;

  BOOL isForMe = pView_->isLocalPlayer(currentPlayer_);

  if (isForMe)
  { 
    if (Global::IsTournament())
    {
      if (Global::Instance().getActionTimeouts() > 2)
      { // More than 2 consecutive time outs -
        // fold immediately
        onFold();
        CActionBar::UncheckActionButtons();
        return TRUE;
      }
    }  
    
    // THIS player's turn - animate action buttons
    CActionBar::Instance()->animate(500);
  }

  turnPtr_.tick(ticks_);

  Player* pP = pView_->getPlayer(currentPlayer_);

  if (ticks_ > waitActionTime_)
  {
    if (pP)
      pP->setActionText("");

    if (isForMe)
    {
      // If the player does not act in time,
      // the default action is to FOLD!
      onFold();

      // Player times out and won't reply -
      // turn sit-out flag automatically on
      CActionBar::CheckSitOut(TRUE);

      Global::Instance().addActionTimeout();
      if (Global::Instance().getActionTimeouts() == 3)
      {
        // Three consecutive time outs => set blind off mode
        AfxMessageBox("You failed to act in time on three consecutive hands. Your chips will be blinded off.\r\nTo continue playing, click ok.");
        // When the above returns the user has clicked on the
        // dialog - reset action timeouts to allow him to play again
        Global::Instance().resetActionTimeouts();
      }
    }
    else
    {
      endPrompt();
    }
  }
  else
  {
    if (isForMe && waitActionTime_ - ticks_ == 4500)
      SndMan::Instance()->playSound(SndMan::SM_WakeUp);
    else if (isForMe && waitActionTime_ - ticks_ == 3500)
      SndMan::Instance()->playSound(SndMan::SM_WakeUp);
    else if (isForMe && waitActionTime_ - ticks_ == 2500)
      SndMan::Instance()->playSound(SndMan::SM_WakeUp);
    else if (isForMe && waitActionTime_ - ticks_ == 1500)
      SndMan::Instance()->playSound(SndMan::SM_WakeUp);
    else if (isForMe && waitActionTime_ - ticks_ == 500)
      SndMan::Instance()->playSound(SndMan::SM_WakeUp);
  }

  return TRUE;
}